/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/

#include "kmemorystream.h"
#include <string.h>

kMemoryStream::kMemoryStream(keStreamOps ops) : kStream(ops) {
	_ownedBuffer = new kDataBuffer();
	_expandBuffer = _ownedBuffer;
	_writeBuffer = _ownedBuffer;
	_readBuffer = _ownedBuffer;
	_writeData = _writeBuffer->getData();
	_readData = _readBuffer->getDataView();
}
kMemoryStream::kMemoryStream(const kDataView& data, keStreamOps ops, bool copy) : kStream(ops) {
	if (copy) {
		_ownedBuffer = new kDataBuffer(data);
		if (canExpand()) {
			_expandBuffer = _ownedBuffer;
		}
		if (canRead()) {
			_readBuffer = _ownedBuffer;
		}
		if (canWrite()) {
			_writeBuffer = _ownedBuffer;
		}
		_writeData = _writeBuffer->getData();
		_readData = _readBuffer->getDataView();
	} else {
		if (canWrite() || canExpand()) {
			throw kError(_T("Cannot write to or expand data source!"));
		}

		_ownedBuffer = 0;
		_expandBuffer = 0;
		_writeBuffer = 0;
		_readBuffer = 0;
		_readData = data;
	}

	_size = _readData.size;
	_available = _size;
	_readOffset = 0;
	_writeOffset = 0;
}
kMemoryStream::kMemoryStream(const kData& data, keStreamOps ops, bool copy) : kStream(ops) {
	if (copy) {
		_ownedBuffer = new kDataBuffer(data);
		if (canExpand()) {
			_expandBuffer = _ownedBuffer;
		}
		if (canRead()) {
			_readBuffer = _ownedBuffer;
		}
		if (canWrite()) {
			_writeBuffer = _ownedBuffer;
		}
		_writeData = _writeBuffer->getData();
		_readData = _readBuffer->getDataView();
	} else {
		if (canExpand()) {
			throw kError(_T("Cannot expand data source!"));
		}

		_ownedBuffer = 0;
		_expandBuffer = 0;
		_writeBuffer = 0;
		_readBuffer = 0;
		_writeData = data;
		_readData = data;
	}

	_size = _readData.size;
	_available = _size;
	_readOffset = 0;
	_writeOffset = 0;
}
kMemoryStream::kMemoryStream(const kDataBufferView* data, keStreamOps ops, bool copy) : kStream(ops) {
	if (copy) {
		_ownedBuffer = new kDataBuffer(data->getDataView());
		if (canExpand()) {
			_expandBuffer = _ownedBuffer;
		}
		if (canRead()) {
			_readBuffer = _ownedBuffer;
		}
		if (canWrite()) {
			_writeBuffer = _ownedBuffer;
		}
		_writeData = _writeBuffer->getData();
		_readData = _readBuffer->getDataView();
	} else {
		if (canWrite() || canExpand()) {
			throw kError(_T("Cannot write to or expand data source!"));
		}

		_ownedBuffer = 0;
		_expandBuffer = 0;
		_writeBuffer = 0;
		_readBuffer = data;
		_readData = data->getDataView();
	}

	_size = _readData.size;
	_available = _size;
	_readOffset = 0;
	_writeOffset = 0;
}
kMemoryStream::kMemoryStream(const kDataBuffer* data, keStreamOps ops, bool copy) : kStream(ops) {
	if (copy) {
		_ownedBuffer = new kDataBuffer(data->getDataView());
		if (canExpand()) {
			_expandBuffer = _ownedBuffer;
		}
		if (canRead()) {
			_readBuffer = _ownedBuffer;
		}
		if (canWrite()) {
			_writeBuffer = _ownedBuffer;
		}
		_writeData = _writeBuffer->getData();
		_readData = _readBuffer->getDataView();
	} else {
		if (canExpand()) {
			throw kError(_T("Cannot expand data source!"));
		}

		_ownedBuffer = 0;
		_expandBuffer = 0;
		_writeBuffer = data;
		_readBuffer = data;
		_writeData = data->getData();
		_readData = data->getDataView();
	}

	_size = _readData.size;
	_available = _size;
	_readOffset = 0;
	_writeOffset = 0;
}
kMemoryStream::kMemoryStream(kDataBuffer* data, keStreamOps ops, bool copy) : kStream(ops) {
	if (copy) {
		_ownedBuffer = new kDataBuffer(data->getDataView());
		if (canExpand()) {
			_expandBuffer = _ownedBuffer;
		}
		if (canRead()) {
			_readBuffer = _ownedBuffer;
		}
		if (canWrite()) {
			_writeBuffer = _ownedBuffer;
		}
		_writeData = _writeBuffer->getData();
		_readData = _readBuffer->getDataView();
	} else {
		_ownedBuffer = 0;
		_expandBuffer = data;
		_writeBuffer = data;
		_readBuffer = data;
		_writeData = data->getData();
		_readData = data->getDataView();
	}

	_size = _readData.size;
	_available = _size;
	_readOffset = 0;
	_writeOffset = 0;
}
size_t kMemoryStream::write(const kDataView& data) {
	if (!canWrite())
		throw kError(_T("Writing to stream is not supported!"));

	if (_writeBuffer)
		_size = _writeBuffer->getSize();

	if (_writeOffset > _size)
		throw kError(_T("Invalid write position!"));

	size_t writeSize = std::min(data.size, _size - _writeOffset);
	if (writeSize < data.size) {
		if (_expandBuffer) {
			_expandBuffer->resize(_writeOffset + data.size);
			_size = _expandBuffer->getSize();
			writeSize = data.size;
		}
	}
	if (_writeBuffer)
		_writeData = _writeBuffer->getData();

	memcpy(_writeData.ptr + _writeOffset, data.ptr, writeSize);
	_writeOffset += writeSize;
	_available = _readOffset < _size ? _size - _readOffset : 0;

	return writeSize;
}
size_t kMemoryStream::read(const kData& data) {
	if (!canRead())
		throw kError(_T("Reading from stream is not supported!"));

	if (_readBuffer) {
		_readData = _readBuffer->getDataView();
		_size = _readBuffer->getSize();
	}

	size_t readSize = std::min(data.size, _size - _readOffset);

	memcpy(data.ptr, _readData.ptr + _readOffset, readSize);
	_readOffset += readSize;
	_available = _readOffset < _size ? _size - _readOffset : 0;

	return readSize;
}
void kMemoryStream::seek(keSeekMode mode, size_t offset) {
	if (!canSeek())
		throw kError(_T("Seeking stream is not supported!"));

	switch (mode) {
	case keSeek_Read:
		_readOffset = offset;
		_available = _readOffset < _size ? _size - _readOffset : 0;
		break;
	case keSeek_Write:
		_writeOffset = offset;
		break;
	default:
		throw kError(_T("Invalid seek mode!"));
	}
}
void kMemoryStream::flush() {
}
kMemoryStream::~kMemoryStream() {
	if (_ownedBuffer) {
		delete _ownedBuffer;
	}
}
